/*
 * @Author: Wangjun
 * @Date: 2023-08-17 11:23:15
 * @LastEditTime: 2023-12-10 11:45:26
 * @LastEditors: wangjun haodreams@163.com
 * @Description:
 * @FilePath: \xr_node_calc\models\task.go
 * hnxr
 */
package models

import (
	//"encoding/json"
	"fmt"
	"hash/crc32"
	"math"
	"os"
	"path/filepath"
	"strings"

	"gitee.com/haodreams/golib/logs"
	"gitee.com/haodreams/libs/ee"
	"gitee.com/hnxr_1/nodex"
)

/**
 * @description: 文件中解析出业务算子
 * @param {string} fileName
 * @return {*}
 */
func parseToTask(parent, fileName string) (b *Task, err error) {
	srcFileName := fileName
	data, err := os.ReadFile(fileName)
	if err != nil {
		logs.Info("加载组态画布失败:", srcFileName, err.Error())
		return nil, ee.Print(fmt.Errorf("%s:%v", srcFileName, err), logs.CbWarn)
	}

	task := new(nodex.Task)
	err = json.Unmarshal(data, task)
	if err != nil {
		logs.Info("加载组态画布失败:", srcFileName, err.Error())
		return nil, ee.Print(fmt.Errorf("%s:%v", srcFileName, err), logs.CbWarn)
	}

	nodes, _, err := task.ToNodes()
	if err != nil {
		logs.Info("加载组态画布失败:", srcFileName, err.Error())
		return nil, ee.Print(fmt.Errorf("%s:%v", srcFileName, err), logs.CbWarn)
	}

	hash := crc32.New(crc32.IEEETable)
	hash.Write(data)
	b = new(Task)

	b.Nodes = nodes
	fileName = filepath.Base(fileName)
	ext := filepath.Ext(fileName)
	fileName = strings.TrimSuffix(fileName, ext)
	b.Relation.FileName = fileName
	b.Relation.Parent = parent
	b.Relation.CRC = fmt.Sprintf("%x", hash.Sum32())
	path := fmt.Sprintf("data/run/%s.%s%s", b.Relation.FileName, b.Relation.CRC, ext)
	os.WriteFile(path, data, 0755)

	for _, node := range nodes {
		node.SetParent(&b.Relation)
		err = node.Init(nil)
		if err != nil {
			logs.Warn(srcFileName, err)
		}
		if node.GetModelID() == nodex.ModOut {
			// if b.Outs != nil {
			// 	return nil, ee.Print(ee.NewError("配置错误,只能有一个输出节点,"+b.Relation.FileName), logs.CbWarn)
			// }
			b.Outs = node.Outs()
			b.resultNode = node
		}
	}
	logs.Info("加载组态画布成功:", srcFileName)
	return
}

/**
 * @description: 文件中解析出业务算子,如果 fileName 是一个目录，说明需要执行一个画布包
 * @param {string} fileName
 * @return {*}
 */
func ParseToTask(fileName string) (bs []*Task, err error) {
	fileName = strings.TrimSuffix(fileName, "/")
	fi, err := os.Stat(fileName)
	if err != nil {
		return
	}
	if fi.IsDir() {
		fs, err := os.ReadDir(fileName)
		if err != nil {
			return nil, err
		}
		//每个画布是独立业务，不合并计算
		isMerge := true
		_, e := os.Stat(fileName + "/.nomerge")
		if e == nil {
			isMerge = false
		}
		parent := filepath.Base(fileName)
		for _, f := range fs {
			if f.IsDir() {
				continue
			}
			if strings.HasSuffix(f.Name(), ".json") {
				b, err := parseToTask(parent, fileName+"/"+f.Name())
				if err == nil {
					bs = append(bs, b)
					if !isMerge {
						b.all = []*Task{b}
					}
				}
			}
		}

		//计算最后一个画布
		n := len(bs)
		if n > 0 && isMerge {
			bs[n-1].all = bs
		}
		return bs, nil
	}

	//没有指定父画布表示是独立画布
	b, err := parseToTask("", fileName)
	if err == nil {
		bs = append(bs, b)
	}
	//计算最后一个画布
	n := len(bs)
	if n > 0 {
		bs[n-1].all = bs
	}
	return
}

/**
 * @description: 关联数据
 * @return {*}
//  */
// type RelationData struct {
// 	Data   any
// 	Extend any //扩展信息
// }

// func NewRelationData(data, extend any) *RelationData {
// 	r := new(RelationData)
// 	r.Data = data
// 	r.Extend = extend
// 	return r
// }

// 画布实例
type Task struct {
	Relation   nodex.Parent
	ID         int //画布id
	mem        Memory
	resultNode nodex.Noder
	Nodes      []nodex.Noder //当前画布的node
	Outs       []*nodex.Out
	Value      float64
	Bad        bool
	//err        error
	Err string
	//Finished   bool

	all []*Task //最后一个画布才有全部的任务列表,如果每个子任务就是一个结果再任务文件加下 .nomerge 文件
	//allNodes []nodex.Noder //全部画布的node
}

/**
 * @description: 复制
 * @return {*}
 */
func (m *Task) Clone(parent *nodex.Parent, mapNodeMem map[string]*nodex.Memory) (mems []*nodex.Memory, b *Task) {
	b = new(Task)
	b.Relation = m.Relation
	b.all = m.all
	b.Relation.RID = parent.RID
	b.Relation.Category = parent.Category
	b.Relation.Data = parent.Data
	b.mem.V = math.NaN()
	//生成计算节点
	mapPort := make(map[string]nodex.Valuer)
	for _, n := range m.Nodes {
		node := n.Clone()
		node.SetParent(&b.Relation)
		ports := node.GetParams()
		for _, port := range ports {
			mapPort[port.GetID()] = port
		}

		b.Nodes = append(b.Nodes, node)
	}

	//绑定连接关系
	for _, port := range mapPort {
		prev := port.GetPrevPort()
		if prev == "" {
			continue
		}
		prevPort := mapPort[prev]
		if prevPort != nil {
			prevPort.Link(port)
		}
	}
	for _, node := range b.Nodes {
		var mem *nodex.Memory
		if mapNodeMem != nil {
			mem = mapNodeMem[node.MakeUID()]
		}
		err := node.Init(mem)
		if err != nil {
			logs.Warn(err)
		}
		if node.GetModelID() == nodex.ModOut {
			b.Outs = node.Outs()
			b.resultNode = node
		}
		mem = node.GetMemory()
		if mem.UID != "" {
			mems = append(mems, mem)
		}
	}
	b.mem.ID = b.Relation.MakeKey() //fmt.Sprintf("%s-%s-%d", m.Relation.FileName, m.Relation.Category, m.Relation.RID)
	return mems, b
}

/**
 * @description: 执行计算
 * @return {*}
 */
func (m *Task) Run(now int64) (err error) {
	m.Relation.ExecTime = now
	for _, node := range m.Nodes {
		e := node.Calc()
		if e != nil {
			err = e
		}
	}
	return
}

/**
 * @description: 和上一个值比较是否相同
 * @return {*}
 */
func (m *Task) SaveWithPrevValue() bool {
	if m.resultNode == nil {
		return true
	}
	value, ng := m.resultNode.GetResult()
	//精度0.01
	if math.Abs(value-m.mem.V) <= 0.01 && ng == m.mem.NG {
		return true
	}
	m.mem.V = value
	m.mem.NG = ng
	return false //需要写入kafka
}

/**
 * @description: 获取计算结果
 * @return {*}
 */
func (m *Task) GetResult() (f float64, isBad bool) {
	if m.resultNode == nil {
		return 0, true
	}
	return m.resultNode.GetResult()
}
